package toman.jpa;

import toman.*;
import toman.orm.TOAnnotQueryEngine;

import javax.persistence.*;
import java.util.*;

/**
 * User: zslajchrt
 * Date: 2.5.11
 * Time: 14:52
 */
public class JPAQueryEngine extends TOAnnotQueryEngine {

    private final EntityManager em;

    public JPAQueryEngine(EntityManager em) {
        this.em = em;
    }

    @Override
    protected <T, K> Object findInitObject(TOMan toman, Class<T> toClass, K toId, String readQueryDef, String idClauseDef) throws Exception {
        String readQuery = readQueryDef + " where " + idClauseDef;
        return executeIdentityQuery(toId, readQuery);
    }

    protected <K> Object executeIdentityQuery(K toId, String readQuery) {
        Query q = em.createQuery(readQuery);
        Parameter<K> idParam = (Parameter<K>) q.getParameters().iterator().next();
        q.setParameter(idParam, toId);

        return q.getSingleResult();
    }

    class JPAQueryForUpdate extends JPAParameterizedQuery implements QueryForUpdate {

        JPAQueryForUpdate(TOMan toman, String queryString) {
            super(toman, queryString);
        }

        public void executeUpdate() {
            q.executeUpdate();
        }
    }

    @Override
    protected <T> QueryForUpdate createQueryForUpdate(TOMan toman, T to, Object updateQueryDef, String idClauseDef) throws Exception {
        final String writeQuery = updateQueryDef.toString() + " WHERE " + idClauseDef;
        return new JPAQueryForUpdate(toman, writeQuery);
    }

    @Override
    protected <T> TOQuery<T> createQuery(TOMan toman, Class<T> toClass, String readQueryDef, Object condition) throws Exception {
        String query;
        if (condition == null || "".equals(condition)) {
            query = readQueryDef;
        } else {
            query = readQueryDef + " where " + condition;
        }

        return new JPAQuery<T>(toman, toClass, query);
    }

    public class JPAParameterizedQuery implements ParameterizedQuery {
        protected final Query q;
        protected final Set<String> paramNames;
        protected final TOMan toman;

        public JPAParameterizedQuery(TOMan toman, String queryString) {
            this.toman = toman;
            q = em.createQuery(queryString);
            HashSet<String> pnames = new HashSet<String>();
            for (Parameter param : q.getParameters()) {
                pnames.add(param.getName());
            }
            paramNames = Collections.unmodifiableSet(pnames);
        }

        public JPAParameterizedQuery setParameter(String paramName, Object value) {
            q.setParameter(paramName, value);
            return this;
        }

        public Set<String> getParameterNames() {
            return paramNames;
        }

        public JPAParameterizedQuery setParameter(String paramName, Date value, TemporalType tt) {
            q.setParameter(paramName, value, tt);
            return this;
        }

    }

    public class JPAQuery<T> extends JPAParameterizedQuery implements TOQuery<T> {

        private final Class<T> toClass;

        JPAQuery(TOMan toman, Class<T> toClass, String queryString) {
            super(toman, queryString);
            this.toClass = toClass;

        }

        public JPAQuery setFirstResult(int index) {
            q.setFirstResult(index);
            return this;
        }

        public JPAQuery setMaxResults(int max) {
            q.setMaxResults(max);
            return this;
        }

        public TOList<T> getResults() {
            List<Object> resultList = q.getResultList();
            return new TOList<T>(resultList, toClass, toman);
        }

        public T getSingleResult() {
            try {
                return toman.createTOInstance(toClass, q.getSingleResult());
            } catch (javax.persistence.NonUniqueResultException e) {
                throw new TOQuery.NonUniqueResultException(e.getMessage(), e);
            } catch (NoResultException e) {
                return null;
            } catch (Exception e) {
                throw new IllegalStateException(e);
            }
        }

        public Query getInternalQuery() {
            return q;
        }
    }

}
